#pragma once
// 协议定制
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sstream>
#include <iostream>
#include <vector>
#include <unordered_map>
#include "Socket.hpp"
#include "Util.hpp"
#include "log.hpp"
#define DEBUG1
#define VERSION "HTTP/1.1"
#define LINE_Sep ": "
#define PARA_Sep "?"
#define ERR_FILE "404.html"

#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERR 500

#define LINE_BREAK "\r\n"
const char *webrootdir = "wwwroot";
const char *homepage = "index.html";
extern Log lg;

// 通过后缀获取content-type
static std::string SuffixDesc(const std::string &suffix)
{
  // 通过后缀跟类型对应表获取对应类型
  static std::unordered_map<std::string, std::string> suffix_type;
  suffix_type.insert({".html", "text/html"});
  suffix_type.insert({".js", "application/javascript"});
  suffix_type.insert({".css", "text/css"});
  suffix_type.insert({".jpg", "image/jpeg "});
  suffix_type.insert({".png", "image/png "});
  auto iter = suffix_type.find(suffix);
  if (iter != suffix_type.end())
  {
    return iter->second;
  }
  return "UnKnow";
}
// 根据描述符返回描述字符串
static std::string CodeDesc(int code)
{
  std::string info;
  switch (code)
  {
  case 200:
    info += "OK";
    break;
  case 404:
    info += "NOT FOUND";
    break;
  case 400:
    info+="BAD REQUEST";
    break;
  case 500:
    info+="SERVER ERROR";
    break;
  default:
  info+="UNKNOW ERROR";
    break;
  }
  return info;
}
// 请求报文
class HttpRequest
{
public:
  std::string request_line;              // 请求行
  std::vector<std::string> request_head; // 响应报头

  std::string blank_line;   // 空行
  std::string request_body; // 请求正文
  bool cgi_;                // CGI标志位，代表是否需要执行cgi机制
public:
  // 解析后的结果
  std::string Method;
  std::string URL_;
  std::string Version_;
  std::unordered_map<std::string, std::string> header_kv; // 用来存放请求报头的kv值
  int content_length;                                     // 请求正文长度（POST方法才会有正文）
  std::string path_;                                      // 资源真实路径
  std::string query_string;                                 // 请求上传上来的参数
  int size_;           // 拷贝的文件的大小字节

public:
  HttpRequest()
      : content_length(0), cgi_(false)
  {
  }
  ~HttpRequest()
  {
  }
};

// 响应报文
class HttpResponse
{
public:
  HttpResponse()
      : status_code_(OK), fd_(-1)
  {
  }
  ~HttpResponse()
  {
  }

public:
  std::string status_line;                // 状态行
  std::vector<std::string> response_head; // 响应报头
  std::string blank_line;                 // 空行
  std::string response_body;              // 响应正文
public:
  int status_code_;    // 状态码
  int fd_;             // 用来处理文件拷贝（磁盘拷贝到用户缓冲区、用户缓冲区拷贝到内核）
  std::string suffix_; // 文件后缀
};

// 读取请求，分析请求，构建响应
class Endpoint
{
  public:
  Endpoint(const int &fd)
      : sock_fd(fd),stop_(false)
  {
  }

private:
  int sock_fd; // 套接字
  HttpRequest http_request;
  HttpResponse http_response;
  bool stop_;//用来标记读取是否出错

private:
  bool RecvHttpRequestLine()
  {
   if( Util::ReadLine(sock_fd, http_request.request_line)>0)
   {
    return true;
   }
   lg(DEBUG,"readline error!!");
   stop_=true;
   return false;
  }
  bool RecvHttpRequestHead()
  {
    std::string line;
    while (line != "\n")
    {
      line.clear();
     if( Util::ReadLine(sock_fd, line)>0)
     {
      http_request.request_head.push_back(line);
     }
     else
     {
      stop_=true;
      break;
     }
    }
    http_request.blank_line = line;

    return !stop_;
  }
  void ParseRequestLine()
  {
    std::stringstream ss(http_request.request_line);
    ss >> http_request.Method >> http_request.URL_ >> http_request.Version_;
  }
  void ParseRequestHeader()
  {
    // 解析请求报头打散到map的kv结构里面
    std::string key;
    std::string value;
    for (auto &e : http_request.request_head)
    {
      // 对每一个行进行字符串分割，分隔符": "
      Util::CutString(e, key, value, LINE_Sep);
      http_request.header_kv.insert({key, value});
      // debug
      // lg(DEBUG,"key:%s",key.c_str());
      // lg(DEBUG,"value:%s",value.c_str());
    }
  }
  bool IsNeedRecvHttpBody()
  {
    // 判断是否需要读取请求正文,如果需要把正文字符个数保存到请求request里的content_
    auto &method = http_request.Method; // 如果方法为POST才需要读取，GET不需要
    if (method == "POST")
    {
      auto iter = http_request.header_kv.find("Content-Length");
      if (iter != http_request.header_kv.end())
      {
        http_request.content_length = atoi(iter->second.c_str());
        return true;
      }
    }
    return false;
  }
  void RecvHttpRequestBody()
  {
    if (IsNeedRecvHttpBody())
    {
      char c;
      int length = http_request.content_length;
      while (length)
      {
        ssize_t n = recv(sock_fd, &c, 1, 0);
        if (n > 0)
        {
          http_request.request_body.push_back(c);
          --length;
        }
      }
    }
        #ifdef DEBUG1
    std::cout << "-----------debug bgin-------" << std::endl;
    std::cout << http_request.request_line;

    for (auto &e : http_request.request_head)
    {
      std::cout << e;
    }
    std::cout << http_request.blank_line;
    std::cout << http_request.request_body;
    std::cout<<std::endl;
    std::cout << "method: " << http_request.Method
              << "   url: " << http_request.URL_
              << "   version: " << http_request.Version_
              << "  path:" << http_request.path_
              << "  para:" << http_request.query_string << std::endl;
    std::cout << "-----------debug end-------" << std::endl;
#endif
  }
  int CgiProcess()
  {
    // 进到这里来必须带参必须是可执行程序
    // 准备工作
    auto &path = http_request.path_;
    auto &method = http_request.Method;
    // cgi机制处理
    // 1.get方法：程序->path ,参数 -> ?后面-> parameter
    // 2.post方法： 程序-> path, 参数 ->body(请求正文)
    // 3.如果让当前进程去程序替换，整个进程会挂掉，-> 让子进程处理
    // 4.如何把参数传给子进程？如何获取子进程处理后的结果？-> 管道通信->如何进行双向通信-> 两个管道！
    // 5.如果是get带参用环境变量传，如果是post带参用管道传

    // 双管道建立
    int input[2] = {0}; // 站在父进程角度从input读、从output写
    int output[2] = {0};
    int i = pipe(input);
    int o = pipe(output);
    if (i < 0 || o < 0)
    {
      lg(ERROR, "pipe error!!!");
      return SERVER_ERR;
    };
  // 替换的线程先从这个环境变量读是GET还是POST
        std::string env = "METHOD=";
        env += method;
        putenv((char *)env.c_str());
    // 创建子进程
    pid_t id = fork();
    if (id == 0)
    {
      // child //程序替换前的处理工作
      close(input[0]);
      close(output[1]);
     
      // 替换后的程序去环境变量里查看GET或POST请求方法，GET去环境变量读参数，POST去管道读参数
      if (method == "GET")
      {
        // 再从这个环境变量读参数
        std::string querystring = "QUERY_STRING=";
        querystring += http_request.query_string;
        putenv((char *)querystring.c_str());
      }
      else if(method=="POST")
      {
        //替换后的进程将来要从管道里读多少字节，也导入环境变量
        std::string length="CONTENT-LENGTH=";
        length+=std::to_string(http_request.content_length);
        putenv((char*)length.c_str());
      }
 // 约定往后子进程读就从标准输入读，写就往标准输出写
      dup2(output[0], 0);
      dup2(input[1], 1);
      // 程序替换
      execl(path.c_str(), path.c_str(), nullptr);
      exit(1);
    }
    else if (id < 0)
    {
      // 创建失败
      lg(ERROR, "Fork error!!");
      return SERVER_ERR;
    }
    else
    {
      //FARTHER
    close(input[1]);
    close(output[0]);
    if (method == "POST")
    {
      auto &body = http_request.request_body;
      int size = body.size(); // 需写入
      int total = 0;          // 已写入
      int n = 0;              // 当前写入
      while (total != size)
      {
        // 管道可能存在一次性写不完的情况（内容太多，管道空间有限）
        n = write(output[1], body.c_str() + total, size - total);
        total += n;
      }
    }
    //不管post还是get的cgi，都把处理结果写入响应正文body中
    char c;
    while(read(input[0],&c,1)>0)//>0读取成功，等于0写端关闭，<0读取失败
    {
      http_response.response_body.push_back(c);
    }
    //debug
    lg(DEBUG,"responsebody:%s",http_response.response_body.c_str());
      // farther ：回收子进程，获取子进程退出信息
      int status;
   pid_t pid=   waitpid(id, &status, 0);
   if(pid==id)
   {
    if(WIFEXITED(status))
    {
      //程序正常退出
      if(WEXITSTATUS(status)==0)
      {
        //退出码正常？？
        return OK;
      }
      else{
        return SERVER_ERR;
      }
    }
    else{
      return SERVER_ERR;
    }
   }
    }
    close(input[0]);
    close(output[1]);
  }
  int NoCgiProcess()
  {
    // 所有关于response的构建都在这写
    http_response.fd_ = open(http_request.path_.c_str(), O_RDONLY);
    if(http_response.fd_>0)
    {
    return OK;
  }
  return NOT_FOUND;
  }
  void BuildOkResponse()
  {
        // 响应报头，添加Content-Length和Content-Type的k v
    std::string headline = "Content-Type: ";
    headline += SuffixDesc(http_response.suffix_);
    headline += LINE_BREAK;
    http_response.response_head.push_back(headline);

    headline = "Content-Length: ";
    if(http_request.cgi_)
    {
    headline += std::to_string(http_response.response_body.size());
    }
    else{
      headline+=std::to_string(http_request.size_);
    }
    headline += LINE_BREAK;
    http_response.response_head.push_back(headline);

    // 空行
    http_response.blank_line = LINE_BREAK;

    //响应正文：如果是cgi程序已经处理好，如果不是cgi程序只需要打开资源文件，到发送的时候直接调用sendfile往发送缓冲区拷贝
  }
  void BuildErrorResponse(const std::string&filename)
  {
    http_response.fd_=open(filename.c_str(),O_RDONLY);
    if(http_response.fd_>0)
    {
      struct stat st;
      stat(filename.c_str(),&st);
      http_request.size_=st.st_size;//获取失败文件字节数，后续发送用到
    }
    else{
      lg(ERROR,"open err.html error!!");
    }
  }
void BuildHttpResponseHandler()
{
  //构建响应状态行
  auto&code=http_response.status_code_;
  auto& statusline=http_response.status_line;
  statusline+=VERSION;
  statusline+=" ";
  statusline+=std::to_string(code);
  statusline+=" ";
  statusline+=CodeDesc(code);
  statusline+=LINE_BREAK;

  //根据code构建不同响应报头、正文
  std::string path=webrootdir;
  path+="/";
  switch(code)
  {
    case 200:
    BuildOkResponse();
    break;
    case 404:
    path+=ERR_FILE;
    BuildErrorResponse(path);
    break;
    case 400:
    break;
    case 500:
    break;
    default:
    break;
  }
}
public:
  void RecvHttpRequest()
  {
    
    if(RecvHttpRequestLine()&& RecvHttpRequestHead())
    {
         ParseRequestLine();
    ParseRequestHeader();
    RecvHttpRequestBody();
    }
  }
  bool Stop()
  {
    return stop_;
  }
  void BuildResponse()
  {
    struct stat st;
    auto&code=http_response.status_code_;
    size_t pos;
    // 1.先判断是否GET或POST方法，目前只支持（后面再修改）
    if (http_request.Method != "GET" && http_request.Method != "POST")
    {
      lg(DEBUG, "请求方法非法！！");
      code = BAD_REQUEST; // 设置状态码
      goto END;
    }
    if (http_request.Method == "POST")
    {
      // 请求正文带参
      http_request.cgi_ = true;
      http_request.path_ = webrootdir;
      http_request.path_ += http_request.URL_;
    }
    else if (http_request.Method == "GET")
    {
      // 如果是GET方法，分析URL有无带参，"?"左边为path，右边为参数,如果没有带参？url就是路径
      auto pos = http_request.URL_.find("?");
      if (pos != std::string::npos)
      {
        // 有参
        http_request.cgi_ = true; // 设置cgi机制

        Util::CutString(http_request.URL_, http_request.path_, http_request.query_string, PARA_Sep);
        // lg(DEBUG,"path:%s, para:%s",http_request.path_.c_str(),http_request.Parameter_.c_str());

        // 输入的URL不是从linux的根目录开始，而是从你的资源文件的目录开始，所以必须要有一个网页根目录
        std::string path = webrootdir; // url: /a/b/c -> wwwroot/a/b/c
        path += http_request.path_;
        http_request.path_ = path;
      }
      else
      {
        // 无参
        http_request.path_ = webrootdir;
        http_request.path_ += http_request.URL_;
      }
    }

    if (http_request.path_[http_request.path_.size() - 1] == '/') // 规定所有最后结尾有'/'处理成首页
    {
      http_request.path_ = webrootdir;
      http_request.path_ += "/";
      http_request.path_ += homepage;
    }

    // 处理完URL后根据path判断路径资源是否有效

    if (stat(http_request.path_.c_str(), &st) == 0) // 用来查看资源是否存在并获取文件信息
    {
      // 成功，存在文件，判断是文件还是目录还是可执行程序
      if (S_ISDIR(st.st_mode))
      {
        // 如果是目录，让它访问该目录的首页
        lg(DEBUG, "这是一个目录！！");
        http_request.path_ += "/";
        http_request.path_ += homepage;
        stat(http_request.path_.c_str(), &st);
      }
      if (S_IXUSR & st.st_mode || S_IXGRP & st.st_mode || S_IXOTH & st.st_mode) // 拥有者、所属组、其他人只要有一个x权限代表文件可执行
      {
        // 如果是可执行程序
        http_request.cgi_ = true;
      }
      http_request.size_= st.st_size; // 保存文件字节数，将来发送数据要使用
    }
    else
    {
      // 失败，文件不存在
      lg(DEBUG, "文件不存在！！errstring:%s", strerror(errno));
      code = NOT_FOUND; // 设置状态码
      goto END;
    }

    // 处理文件后缀，后续用来构建报头k 、v（type）
   pos = http_request.path_.rfind(".");
    if (pos != std::string::npos)
    {
      http_response.suffix_ = http_request.path_.substr(pos);
    }

    // 走到这里cgi标志位已设置，path路径、参数已设置
    // 根据是否执行cgi机制进行处理
    if (http_request.cgi_)
    {
      // 执行cgi机制
     code= CgiProcess();//把处理结果写到response.body中了
    }
    else
    {
      // 不用执行cgi
     code= NoCgiProcess();//只需要打开静态网页文件
    }
END:
    BuildHttpResponseHandler();
  }
  void SendResponse()
  {
    // 发送响应
    // 状态行
    send(sock_fd, http_response.status_line.c_str(), http_response.status_line.size(), 0);

    // 报头
    for (auto &e : http_response.response_head)
    {
      send(sock_fd, e.c_str(), e.size(), 0);
    }

    // 空行
    send(sock_fd, http_response.blank_line.c_str(), http_response.blank_line.size(), 0);

    // 响应正文
    if(!http_request.cgi_)
    {
      //非cgi正文==资源文件
    ssize_t n = sendfile(sock_fd, http_response.fd_, nullptr, http_request.size_);
    if (n < 0)
      lg(FATAL, "errstring is: %s", strerror(errno));
    close(http_response.fd_);
    }
    else{
      //cgi正文==response.body
      const char*str=http_response.response_body.c_str();
      size_t size=http_response.response_body.size();
      size_t total=0;//已发送
      size_t n=0;//当前发送
     while(total<size&& (n=write(sock_fd,str+total,size-total))>0)//
     {
      total+=n;
     }
    }
  }
  ~Endpoint()
  {
    close(sock_fd);
  }
};

// 线程入口函数
class CallBack
{
public:
CallBack()
{

}
  void operator()(int sock)//仿函数
  {
    HandlerRequest(sock);
  }
  void HandlerRequest(int sock)
  {
    Endpoint *ep = new Endpoint(sock);
    lg(DEBUG,"sockfd is: %d",sock);
    ep->RecvHttpRequest();
    lg(DEBUG,"stop is:%d",ep->Stop());
    if(!ep->Stop())
    {
      lg(INFO,"Recv success!! no error!");
        ep->BuildResponse();
    ep->SendResponse();
    }
    else
    {
      lg(INFO,"Recv error!");

    }
    delete ep;
    lg(DEBUG, "ONCE request finish!!");
  }
  ~CallBack()
  {
    
  }
};